Skip to content

fix(jsonrpc): preserve absolute parse error columns - #4020

Open
zakazaka95 wants to merge 3 commits into
NethermindEth:mainfrom
zakazaka95:fix/3914-jsonrpc-absolute-column
Open

fix(jsonrpc): preserve absolute parse error columns#4020
zakazaka95 wants to merge 3 commits into
NethermindEth:mainfrom
zakazaka95:fix/3914-jsonrpc-absolute-column

Conversation

@zakazaka95

@zakazaka95 zakazaka95 commented Sep 1, 2026

Copy link
Copy Markdown

Description

JSON-RPC parse errors on lines longer than the 512-byte recovery window reported a column relative to the retained window instead of the original line. This tracks the discarded rune prefix while keeping the recovery buffer bounded.

The caret remains relative to the retained text, while the reported column is absolute. Window boundaries stay UTF-8-safe, and the counting path uses a no-newline fast path plus 64-bit continuation-byte masks to avoid per-rune decoding.

Fixes #3914.

Testing

  • go test ./jsonrpc
  • go vet ./jsonrpc
  • focused regressions for a large single write, split UTF-8 input, discarded newlines, and caret alignment
  • mixed ASCII and 2/3/4-byte Unicode, including a continuation sequence split across 127-byte reads
  • updated end-to-end long-line expectation (absolute column 766)

Benchmarks

Compared upstream main at 963f145 with this PR at b87a330 on Go 1.27.0 (windows/amd64), Ryzen 7 5700X, GOMAXPROCS=1:

go test ./jsonrpc -run '^$' -bench '^(BenchmarkHandle|BenchmarkHandleLargeRequest)$' -benchmem -benchtime=2s -count=5 -cpu=1

Five-run medians:

Benchmark main PR Delta
BenchmarkHandle 3,530 ns/op 3,610 ns/op +2.27%
BenchmarkHandleLargeRequest/1MB 4.842 ms/op 4.962 ms/op +2.49%
BenchmarkHandleLargeRequest/10MB 44.198 ms/op 45.214 ms/op +2.30%

The five-run ranges overlap for all three benchmarks. Allocations remain unchanged for BenchmarkHandle (24 allocs/op) and the 1 MB case (56 allocs/op).

@zakazaka95
zakazaka95 force-pushed the fix/3914-jsonrpc-absolute-column branch from 427ba4b to 4ebcf73 Compare September 1, 2026 21:44

@RafaelGranza RafaelGranza left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your PR!
Please take a look at my comments, and after you address all of them, this work will nicely fix the open issue.

Comment thread jsonrpc/pretty_error.go Outdated
Comment thread jsonrpc/pretty_error_internal_test.go Outdated
Comment thread jsonrpc/pretty_error_test.go Outdated
Comment on lines +232 to +240

func TestHandleParseErrorKeepsAbsoluteColumnForLongUnicodeLine(t *testing.T) {
server := jsonrpc.NewServer(1, log.NewNopZapLogger())
req := `{"jsonrpc":"2.0","method":"x","padding":"` + strings.Repeat("👍", 160) + `","id":@}`

res, _, err := server.HandleReader(t.Context(), strings.NewReader(req))
require.NoError(t, err)
assert.Contains(t, string(res), `[line 1, position 209]`)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can use TestHandleParseError to exercise this case.

Comment thread jsonrpc/pretty_error.go
Comment thread jsonrpc/pretty_error.go
Comment thread jsonrpc/pretty_error_test.go

@RafaelGranza RafaelGranza left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please run BenchmarkHandle and BenchmarkHandleLargeRequest benchmarks from file jsonrpc/server_test.go, compare it to main, and post the numbers in the PR description.

chunkSize int
position string
marker byte
}{

@RafaelGranza RafaelGranza Sep 3, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is not needed.
Just adding entries to parseErrorTests would cover the new changes. And please cover the case when there are broken runes (part of it is no longer in the window).

If you find it is not possible, for any reason, please let me know, and lets move back to having 2 different tests.

@codecov

codecov Bot commented Sep 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 79.18%. Comparing base (e85a15e) to head (b87a330).
⚠️ Report is 14 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4020      +/-   ##
==========================================
- Coverage   79.19%   79.18%   -0.02%     
==========================================
  Files         464      464              
  Lines       35741    35828      +87     
==========================================
+ Hits        28305    28369      +64     
- Misses       7427     7450      +23     
  Partials        9        9              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@RafaelGranza RafaelGranza left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks again for you contribution, and also for addressing the previous comments.

I still have some comments I'd like you to give a look before proceeding with my review.

Comment thread jsonrpc/pretty_error.go
line, col := lineAndColumn(c, markerPos)
msg := fmt.Sprintf("%s [line %d, position %d]", describeError(c.window, markerPos, err), line, col)
line, relativeCol, absoluteCol := lineAndColumn(c, markerPos)
msg := fmt.Sprintf("%s [line %d, position %d]", describeError(c.window, markerPos, err), line, absoluteCol)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our linter is pointing this line is too long, breaking it in two should fix it.

req: strings.Repeat(" ", 600) + "\n" + `{"padding":"` + strings.Repeat("👍", 160) + `","id":@` + strings.Repeat("x", 96) + `}`,
chunkSize: 127,
position: `[line 2, position 180]`,
marker: '@',

@RafaelGranza RafaelGranza Sep 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO we don't need this, we can use the same res field here.

The way we are doing with requireMarkerUnderByte is making the same marker position calculations as in prod and checking if they match. But this introduce a problem, how do we know they are not both equally wrong?

IMO, the right call would be not checking the internal behavior of pretty_error.go, just checking the error output is correct. How the computations are made internally don't matter that much.

If you are worried that the res field could get too large, just remember the printed message is capped by 512 bytes, so res is not that large.

@RafaelGranza RafaelGranza Sep 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you disagree with this approach, let me know.

But anyway, if you want to keep the chunkSize field, we should move to having two distinct tests, because TestHandleParseError increases a lot in complexity with these added fields.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

jsonrpc: parse error shows the wrong column on long requests

2 participants